package pl.edu.agh.io.fx3.monitor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

import pl.edu.agh.io.fx3.exceptions.FileAlreadyExistsException;
import pl.edu.agh.io.fx3.filesystem.FileSystemFactory;
import pl.edu.agh.io.fx3.filesystem.disk.DiskFileSystem;
import pl.edu.agh.io.fx3.saver.dao.FileUtilsDAO;
import pl.edu.agh.io.fx3.saver.model.FileInfo;
import pl.edu.agh.io.fx3.treecreator.disk.DiskTreeConfig;
import pl.edu.agh.io.fx3.treecreator.disk.DiskTreeCreator;
import pl.edu.agh.io.fx3.utils.ChecksumGenerator;

/**
 * Class which makes up an abstract representation of file and directory
 * pathnames. It is a wrapper to java.io.File implementation.
 * 
 * @author Jaroslaw Janik
 */
public class VFSFile implements IFile {

	private String pathname;
	private java.io.File file;
	private DiskTreeCreator treeCreator;
	private FileUtilsDAO dao;
	private DiskFileSystem diskFileSystem;

	/**
	 * Constructor which gets a pathname that is seen for user in vfs. It also
	 * gets an instance of TreeCreator to which it will delegate a control of
	 * folder's hierarchy management.
	 * 
	 * @param pathname
	 *            A virtual pathname seen for user's perspective.
	 * @param treeCreator
	 *            An instance of object responsible for hierarchy management.
	 */
	public VFSFile(String pathname, DiskTreeCreator treeCreator,
			FileUtilsDAO dao, DiskFileSystem diskFileSystem) {
		this.treeCreator = treeCreator;
		this.pathname = pathname;
		this.dao = dao;
		this.diskFileSystem = diskFileSystem;

		String realPath;
		try {
			realPath = dao.getRealPath(pathname);

			if (!realPath.startsWith("dir_")) {
				this.file = new File(realPath);
			}
		} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
			FileSystemFactory.LOGGER.error(e);
		}
		
		FileSystemFactory.LOGGER.debug("New VFSFile instance created.");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#canRead()
	 */
	public boolean canRead() {
		
		FileSystemFactory.LOGGER.debug("Asking if " + file + " can be read.");
		if (file == null) {
			FileSystemFactory.LOGGER.info("File " + file + " can't be read.");
			return false;
		}
		
		FileSystemFactory.LOGGER.info("File " + file + (file.canRead() ? " can " : " can't ") + "be read.");
		return file.canRead();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#canWrite()
	 */
	public boolean canWrite() {
		
		FileSystemFactory.LOGGER.debug("Asking if " + file + " can be written.");
		if (file == null) {
			FileSystemFactory.LOGGER.info("File " + file + " can't be written.");
			return false;
		}

		FileSystemFactory.LOGGER.info("File " + file + (file.canWrite() ? " can " : " can't ") + "be written.");
		return file.canWrite();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#createNewFile()
	 */
	public boolean createNewFile() throws IOException,
			FileAlreadyExistsException {

		FileSystemFactory.LOGGER.debug("Trying to create new file: " + file);
		try {
			if (!isDirectory()) {
				String name = getName();
				if (pathname.length() > name.length()) {
					name = pathname.substring(
							0,
							pathname.length() - name.length()
									- File.separator.length());
				} else {
					name = "";
				}

				if (name.compareTo("") == 0 || dao.exists(name)) {
					file = treeCreator.addFile(pathname);
					if (file != null) {
						FileSystemFactory.LOGGER.info("File " + file + " created.");
						return true;
					}
				}
			}
		} catch (FileAlreadyExistsException e) {
			FileSystemFactory.LOGGER.error(e);
			throw e;
		}

		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#delete()
	 */
	public boolean delete() {

		FileSystemFactory.LOGGER.debug("Trying to delete file: " + file);
		if (file == null) {
			if (isDirectory()) {
				IFile[] files = listFiles();
				if (files == null) {
					try {
						dao.deleteUsingVirtualPath(pathname);
						FileSystemFactory.LOGGER.info("File " + file + " deleted.");
						return true;
					} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
						FileSystemFactory.LOGGER.error(e);
						return false;
					}
				} else {
					return false;
				}
			} else {
				return false;
			}
		}

		return treeCreator.deleteFile(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getFileInputStream()
	 */
	public FileInputStream getFileInputStream() throws FileNotFoundException {
		FileSystemFactory.LOGGER.debug("Trying to obtain FileInputStream from file: " + file);
		if (file == null) {
			return null;
		}

		return new FileInputStream(file);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getFileOutputStream()
	 */
	public FileOutputStream getFileOutputStream() throws FileNotFoundException,
			IOException, FileAlreadyExistsException {

		FileSystemFactory.LOGGER.debug("Trying to obtain FileOutputStream from file: " + file);
		if (file == null) {
			try {
				if (!createNewFile()) {
					return null;
				}
			} catch (FileAlreadyExistsException e) {
				FileSystemFactory.LOGGER.error(e);
				throw e;
			}
		}

		return new FileOutputStream(file);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getFileReader()
	 */
	public FileReader getFileReader() throws IOException {
		
		FileSystemFactory.LOGGER.debug("Trying to obtain FileReader from file: " + file);
		if (file == null) {
			return null;
		}

		return new FileReader(file);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getFileWriter()
	 */
	public FileWriter getFileWriter() throws IOException,
			FileAlreadyExistsException {
		
		FileSystemFactory.LOGGER.debug("Trying to obtain FileWriter from file: " + file);
		if (file == null) {
			try {
				if (!createNewFile()) {
					return null;
				}
			} catch (FileAlreadyExistsException e) {
				FileSystemFactory.LOGGER.error(e);
				throw e;
			}
		}

		return new FileWriter(file);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getName()
	 */
	public String getName() {
		
		FileSystemFactory.LOGGER.debug("Getting name of file " + file);
		String[] splitPath = pathname.split(Pattern.quote(File.separator));

		return splitPath[splitPath.length - 1];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getPath()
	 */
	public String getPath() {
		FileSystemFactory.LOGGER.debug("Getting path of file " + file);
		return pathname;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getParentFile()
	 */
	public IFile getParentFile() {
		
		FileSystemFactory.LOGGER.debug("Getting parent file of file " + file);
		String parentPath = null;
		VFSFile fileParent;

		try {
			parentPath = dao.getParentDir(pathname);
			fileParent = (VFSFile) diskFileSystem.getIFile(parentPath);
		} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
			FileSystemFactory.LOGGER.error(e);
			return null;
		}

		return fileParent;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#getRealPath()
	 */
	public String getRealPath() {
		
		FileSystemFactory.LOGGER.debug("Getting real path of file " + file);
		if (file == null) {
			return null;
		}

		return file.getPath();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#isDirectory()
	 */
	public boolean isDirectory() {
		
		FileSystemFactory.LOGGER.debug("Asking if " + file + " is directory.");
		try {
			if (dao.getRealPath(pathname).startsWith("dir_")) {
				FileSystemFactory.LOGGER.info("File " + file + " is directory.");
				return true;
			}
			
			FileSystemFactory.LOGGER.info("File " + file + " is not directory.");
			return false;
		} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
			FileSystemFactory.LOGGER.error(e);
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#isFile()
	 */
	public boolean isFile() {
		
		FileSystemFactory.LOGGER.debug("Asking if " + file + " is file.");
		if (file == null) {
			FileSystemFactory.LOGGER.info("File " + file + " is not file.");
			return false;
		}

		FileSystemFactory.LOGGER.info("File " + file + " is " + (file.isFile() ? "" : "not ") + " file.");
		return file.isFile();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#isHidden()
	 */
	public boolean isHidden() {
		
		FileSystemFactory.LOGGER.debug("Asking if " + file + " is hidden.");
		if (file == null) {
			FileSystemFactory.LOGGER.info("File " + file + " is not hidden.");
			return false;
		}

		FileSystemFactory.LOGGER.info("File " + file + " is " + (file.isHidden() ? "" : "not ") + "hidden.");
		return file.isHidden();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#length()
	 */
	public long length() {
		
		FileSystemFactory.LOGGER.debug("Getting length of file " + file);
		if (file == null) {
			FileSystemFactory.LOGGER.info("File " + file + " is empty.");
			return 0L;
		}

		FileSystemFactory.LOGGER.info("File " + file + " is " + file.length() + " long.");
		return file.length();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#listFiles()
	 */
	public IFile[] listFiles() {
		List<FileInfo> fileInfoList = new LinkedList<FileInfo>();
		List<VFSFile> fileList = new LinkedList<VFSFile>();

		try {
			fileInfoList = dao.listDir(pathname);
		} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
			FileSystemFactory.LOGGER.error(e);
			return null;
		}

		if (!fileInfoList.isEmpty()) {
			for (FileInfo fileInfo : fileInfoList) {
				fileList.add((VFSFile) diskFileSystem.getIFile(fileInfo
						.getVirtualPath()));
			}

			return fileList.toArray(new VFSFile[fileList.size()]);
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see pl.edu.agh.io.fx3.monitor.IFile#mkdir()
	 */
	public boolean mkdir() {
		
		FileSystemFactory.LOGGER.debug("Creating directory " + file);
		try {
			if (dao.getRealPath(pathname).startsWith("dir_")) {
				FileSystemFactory.LOGGER.info("Directory " + file + " not created.");
				return false;
			}
		} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
			FileSystemFactory.LOGGER.error(e);
			try {
				String name = getName();
				if (pathname.length() > name.length()) {
					name = pathname.substring(
							0,
							pathname.length() - name.length()
									- File.separator.length());
				} else {
					name = "";
				}

				if (name.compareTo("") == 0 || dao.exists(name)) {
					String dirName = "dir_" + randomString();
					dao.insert(pathname, dirName,
							ChecksumGenerator.getChecksum(pathname, dirName));
					FileSystemFactory.LOGGER.info("Directory " + file + " created.");
					return true;
				}
			} catch (FileAlreadyExistsException m) {
				FileSystemFactory.LOGGER.error(m);
				return false;
			}
		}

		FileSystemFactory.LOGGER.info("Directory " + file + " not created.");
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * pl.edu.agh.io.fx3.monitor.IFile#renameTo(pl.edu.agh.io.fx3.monitor.IFile)
	 */
	public boolean renameTo(IFile file) throws NullPointerException {
		
		FileSystemFactory.LOGGER.debug("Renaming file " + this.file + " to " + file);
		if (file == null) {
			NullPointerException e = new NullPointerException();
			FileSystemFactory.LOGGER.error(e);
			throw e;
		}

		if (dao.exists(file.getPath())) {
			FileSystemFactory.LOGGER.info("File " + this.file + " not renamed.");
			return false;
		} else {
			try {
				if (isDirectory()) {
					IFile[] files = listFiles();
					if (files != null) {
						for (IFile newFile : files) {
							String path = newFile.getPath();
							path = file.getPath()
									+ path.substring(pathname.length(),
											path.length());
							dao.changeVirtualPath(newFile.getPath(), path);
						}

						dao.changeVirtualPath(pathname, file.getPath());
						pathname = file.getPath();
					} else {
						FileSystemFactory.LOGGER.info("File " + this.file + " not renamed.");
						return false;
					}
				} else {
					dao.changeVirtualPath(pathname, file.getPath());
					pathname = file.getPath();
				}
			} catch (pl.edu.agh.io.fx3.exceptions.FileNotFoundException e) {
				FileSystemFactory.LOGGER.error(e);
				return false;
			}
		}
		
		FileSystemFactory.LOGGER.info("File " + this.file + " renamed.");
		return true;
	}
	
	@Override
	public String toString() {
		return file.toString();
	}

	private String randomString() {
		UUID uuid = UUID.randomUUID();
		String myRandom = uuid.toString();

		return myRandom.substring(0, DiskTreeConfig.FOLDER_NAME_LENGTH);
	}
}